#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>

#define QTD_PRODUTOS 10
#define QTD_PRODUTORES 3
#define QTD_CONSUMIDORES 5
#define TAM_FILA 5

typedef struct { 
	int livre;
	int x;	 
} mystruct;

int n_produzidos, n_consumidos;

mystruct fila[TAM_FILA];

//controla o acesso as variaveis de producao e consumo
sem_t mutex[TAM_FILA];

//começa com 0. Caso 0 não é possível consumir.
sem_t n_produtos;

//começa com TAM_FILA. Caso 0 não é possível produzir.
sem_t espaco_fila;
	
void *run_produtor(void *arg) {
	long long int num = (long long int) arg;
	int i, value;

	while (1) {
		if (QTD_PRODUTOS - n_produzidos <= num) {
			printf("\n----------> P%d: Bye bye", num);
			pthread_exit(NULL);
		}

		sem_wait(&espaco_fila);

		for(i = 0; i < TAM_FILA; i++) {
			value = sem_trywait(&mutex[i]);
			if (value == 0) {
				if (fila[i].livre == 1) {
					printf("\nP%d I%d", num, n_produzidos);
					fila[i].x = n_produzidos;
					fila[i].livre = 0;
					n_produzidos++;

					sem_post(&n_produtos);
					sem_post(&mutex[i]);
					break;
				}
				sem_post(&mutex[i]);
			}

		}
		sem_post(&n_produtos);

		for(i=0; i<10000000; i++);
	}
}

void *run_consumidor(void *arg) {
	long long int num = (long long int) arg;

	int i, value;
	while (1) {
		if (QTD_PRODUTOS - n_consumidos <= num) {
			printf("\n----------> C%d: Bye bye", num);
			pthread_exit(NULL);
		}
		sem_wait(&n_produtos);

		for(i=0; i<rand() * 100; i++);

		for(i = 0; i < TAM_FILA; i++) {
			value = sem_trywait(&mutex[i]);
			if (value == 0) {
				if (fila[i].livre == 0) {
					printf("\nC%d I%d", num, fila[i].x);
					fila[i].livre = 1;
					n_consumidos++;

					sem_post(&mutex[i]);
					sem_post(&espaco_fila);
					break;
				}
				sem_post(&mutex[i]);
			}

		}
		
		sem_post(&espaco_fila);
	}
}

int main(void) {
	int qtd_threads, i;
	pthread_t *threads;

	n_produzidos = 0;
	n_consumidos = 0;
	
	sem_init(&n_produtos, 0, 0);  
	sem_init(&espaco_fila, 0, TAM_FILA);  
	
	for (i=0; i < TAM_FILA; i++) {
		fila[i].livre = 1;	
		sem_init(&mutex[i], 0, 1);
	}

	qtd_threads = QTD_CONSUMIDORES + QTD_PRODUTORES;

	threads = (pthread_t*) malloc (qtd_threads * sizeof(pthread_t));

	//Cria as threads de consumidores
	for (i = 0; i < QTD_CONSUMIDORES; i++) 
		pthread_create(&threads[i], NULL, run_consumidor, (void *) i);

	//Cria as threads de produtores
	for (i = 0; i < QTD_PRODUTORES; i++)
		pthread_create(&threads[i+QTD_CONSUMIDORES], NULL, run_produtor, (void *) i);

	for (i = 0; i < qtd_threads; i++)
		pthread_join(threads[i], NULL);		

	printf("\n\n### QTD_PRODUTOS=%d n_produzidos=%d n_consumidos=%d\n", QTD_PRODUTOS, n_produzidos, n_consumidos);

	return 0;
}

